﻿// Decompiled with JetBrains decompiler
// Type: TaleWorlds.CampaignSystem.CharacterDevelopment.HeroDeveloper
// Assembly: TaleWorlds.CampaignSystem, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: E85F8C15-4DF6-4E9C-A58A-29177E40D07A
// Assembly location: D:\steam\steamapps\common\Mount & Blade II Bannerlord\bin\Win64_Shipping_Client\TaleWorlds.CampaignSystem.dll

using System;
using System.Collections.Generic;
using System.Linq;
using TaleWorlds.CampaignSystem.Extensions;
using TaleWorlds.Core;
using TaleWorlds.Library;
using TaleWorlds.ObjectSystem;
using TaleWorlds.SaveSystem;

#nullable disable
namespace TaleWorlds.CampaignSystem.CharacterDevelopment
{
  public class HeroDeveloper : PropertyOwnerF<PropertyObject>, IHeroDeveloper
  {
    [SaveableField(100)]
    private CharacterSkills _newFocuses;
    [SaveableField(130)]
    private int _totalXp;

    internal static void AutoGeneratedStaticCollectObjectsHeroDeveloper(
      object o,
      List<object> collectedObjects)
    {
      ((MBObjectBase) o).AutoGeneratedInstanceCollectObjects(collectedObjects);
    }

    protected override void AutoGeneratedInstanceCollectObjects(List<object> collectedObjects)
    {
      base.AutoGeneratedInstanceCollectObjects(collectedObjects);
      collectedObjects.Add((object) this._newFocuses);
      collectedObjects.Add((object) this.Hero);
    }

    internal static object AutoGeneratedGetMemberValueUnspentFocusPoints(object o)
    {
      return (object) ((HeroDeveloper) o).UnspentFocusPoints;
    }

    internal static object AutoGeneratedGetMemberValueUnspentAttributePoints(object o)
    {
      return (object) ((HeroDeveloper) o).UnspentAttributePoints;
    }

    internal static object AutoGeneratedGetMemberValueHero(object o)
    {
      return (object) ((HeroDeveloper) o).Hero;
    }

    internal static object AutoGeneratedGetMemberValue_newFocuses(object o)
    {
      return (object) ((HeroDeveloper) o)._newFocuses;
    }

    internal static object AutoGeneratedGetMemberValue_totalXp(object o)
    {
      return (object) ((HeroDeveloper) o)._totalXp;
    }

    [SaveableProperty(101)]
    public int UnspentFocusPoints { get; set; }

    [SaveableProperty(102)]
    public int UnspentAttributePoints { get; set; }

    public bool IsDeveloperInitialized => this.Hero != null;

    [SaveableProperty(103)]
    public Hero Hero { get; private set; }

    public int TotalXp
    {
      get => this._totalXp;
      private set => this._totalXp = value;
    }

    public int GetSkillXpProgress(SkillObject skill)
    {
      int skillValue = this.Hero.GetSkillValue(skill);
      return MathF.Round(this.GetPropertyValue((PropertyObject) skill)) - Campaign.Current.Models.CharacterDevelopmentModel.GetXpRequiredForSkillLevel(skillValue);
    }

    internal HeroDeveloper(Hero hero)
    {
      this.Hero = hero;
      this._newFocuses = new CharacterSkills();
    }

    public void ClearUnspentPoints()
    {
      this.UnspentAttributePoints = 0;
      this.UnspentFocusPoints = 0;
    }

    public void ClearHero()
    {
      this.ClearAllProperty();
      this.ClearFocuses();
      this.Hero.ClearAttributes();
      this.Hero.ClearSkills();
      this.Hero.ClearPerks();
      this.UnspentFocusPoints = 0;
      this.UnspentAttributePoints = 0;
      this.Hero.ClearTraits();
      this.ClearHeroLevel();
    }

    public void InitializeHeroDeveloper(bool isByNaturalGrowth = false, CharacterObject template = null)
    {
      this.DeriveSkillsFromTraits(isByNaturalGrowth, template);
      this.SetInitialLevelFromSkills();
      this.CheckLevel();
      this.SetupDefaultPoints();
      this.SetInitialFocusAndAttributePoints();
      if (this.Hero.IsChild)
        return;
      this.DevelopCharacterStats();
    }

    public void DevelopCharacterStats()
    {
      if (this.UnspentAttributePoints > 0)
        this.DistributeUnspentAttributePoints();
      if (this.UnspentFocusPoints > 0)
        this.DistributeUnspentFocusPoints();
      this.SelectPerks();
    }

    public int GetTotalSkillPoints()
    {
      int totalSkillPoints = 0;
      foreach (SkillObject skill in (List<SkillObject>) Skills.All)
        totalSkillPoints += this.Hero.GetSkillValue(skill);
      return totalSkillPoints;
    }

    public void ChangeSkillLevel(SkillObject skill, int changeAmount, bool shouldNotify = true)
    {
      int skillValue = this.Hero.GetSkillValue(skill);
      int num1 = skillValue + changeAmount;
      float num2 = 0.0f - (this.GetPropertyValue((PropertyObject) skill) - (float) Campaign.Current.Models.CharacterDevelopmentModel.GetXpRequiredForSkillLevel(skillValue));
      for (int skillLevel = skillValue + 1; skillLevel <= num1; ++skillLevel)
        num2 += (float) (Campaign.Current.Models.CharacterDevelopmentModel.GetXpRequiredForSkillLevel(skillLevel) - Campaign.Current.Models.CharacterDevelopmentModel.GetXpRequiredForSkillLevel(skillLevel - 1));
      this.AddSkillXp(skill, num2 + 1f, false, shouldNotify);
    }

    private void DeriveSkillsFromTraits(bool isByNaturalGrowth = false, CharacterObject template = null)
    {
      foreach (Tuple<SkillObject, int> derivedFromTrait in Campaign.Current.Models.CharacterDevelopmentModel.GetSkillsDerivedFromTraits(this.Hero, template, isByNaturalGrowth))
      {
        SkillObject skill = derivedFromTrait.Item1;
        int newSkillValue = derivedFromTrait.Item2;
        if (this.Hero.GetSkillValue(skill) < newSkillValue)
          this.SetInitialSkillLevel(skill, newSkillValue);
      }
    }

    public void SetInitialSkillLevel(SkillObject skill, int newSkillValue)
    {
      int requiredForSkillLevel = Campaign.Current.Models.CharacterDevelopmentModel.GetXpRequiredForSkillLevel(newSkillValue);
      this.SetPropertyValue((PropertyObject) skill, (float) requiredForSkillLevel);
      this.Hero.SetSkillValue(skill, newSkillValue);
      this.InitializeSkillXp(skill);
    }

    public void AddSkillXp(
      SkillObject skill,
      float rawXp,
      bool isAffectedByFocusFactor = true,
      bool shouldNotify = true)
    {
      if ((double) rawXp <= 0.0)
        return;
      if (isAffectedByFocusFactor)
        this.GainRawXp(rawXp, shouldNotify);
      float num1 = rawXp * Campaign.Current.Models.GenericXpModel.GetXpMultiplier(this.Hero);
      if ((double) num1 <= 0.0)
        return;
      double propertyValue = (double) this.GetPropertyValue((PropertyObject) skill);
      float focusFactor = this.GetFocusFactor(skill);
      double num2 = isAffectedByFocusFactor ? (double) num1 * (double) focusFactor : (double) num1;
      float skillXp = (float) (propertyValue + num2);
      int skillLevelChange = Campaign.Current.Models.CharacterDevelopmentModel.GetSkillLevelChange(this.Hero, skill, skillXp);
      this.SetPropertyValue((PropertyObject) skill, skillXp);
      if (skillLevelChange <= 0)
        return;
      this.ChangeSkillLevelFromXpChange(skill, skillLevelChange, shouldNotify);
    }

    private void GainRawXp(float rawXp, bool shouldNotify)
    {
      if ((long) this._totalXp + (long) MathF.Round(rawXp) < (long) Campaign.Current.Models.CharacterDevelopmentModel.GetMaxSkillPoint())
      {
        this._totalXp += MathF.Round(rawXp);
        this.CheckLevel(shouldNotify);
      }
      else
        this._totalXp = Campaign.Current.Models.CharacterDevelopmentModel.GetMaxSkillPoint();
    }

    public float GetFocusFactor(SkillObject skill)
    {
      return Campaign.Current.Models.CharacterDevelopmentModel.CalculateLearningRate(this.Hero, skill);
    }

    private void ChangeSkillLevelFromXpChange(
      SkillObject skill,
      int changeAmount,
      bool shouldNotify = false)
    {
      if (changeAmount == 0)
        return;
      int num = this.Hero.GetSkillValue(skill) + changeAmount;
      this.Hero.SetSkillValue(skill, num);
      CampaignEventDispatcher.Instance.OnHeroGainedSkill(this.Hero, skill, changeAmount, shouldNotify);
    }

    void IHeroDeveloper.AfterLoad() => this.AfterLoadInternal();

    internal void CheckLevel(bool shouldNotify = false)
    {
      bool flag = false;
      int totalXp = this._totalXp;
      while (!flag)
      {
        int requiredForLevel = this.GetXpRequiredForLevel(this.Hero.Level + 1);
        if (requiredForLevel != Campaign.Current.Models.CharacterDevelopmentModel.GetMaxSkillPoint() && totalXp >= requiredForLevel)
        {
          ++this.Hero.Level;
          this.OnGainLevel(shouldNotify);
        }
        else
          flag = true;
      }
    }

    public void SetInitialLevel(int level) => this.TotalXp = this.GetXpRequiredForLevel(level) + 1;

    private void SetupDefaultPoints()
    {
      this.UnspentFocusPoints = (this.Hero.Level - 1) * Campaign.Current.Models.CharacterDevelopmentModel.FocusPointsPerLevel + Campaign.Current.Models.CharacterDevelopmentModel.FocusPointsAtStart;
      this.UnspentAttributePoints = (this.Hero.Level - 1) / Campaign.Current.Models.CharacterDevelopmentModel.LevelsPerAttributePoint + Campaign.Current.Models.CharacterDevelopmentModel.AttributePointsAtStart;
    }

    private void SetInitialLevelFromSkills()
    {
      this.TotalXp = MathF.Max(1, (int) Skills.All.Sum<SkillObject>((Func<SkillObject, float>) (s => 2f * MathF.Pow((float) this.Hero.GetSkillValue(s), 2.2f))) - 2000);
    }

    private void SetInitialFocusAndAttributePoints()
    {
      foreach (CharacterAttribute characterAttribute in (List<CharacterAttribute>) Attributes.All)
      {
        int attributeValue = this.Hero.GetAttributeValue(characterAttribute);
        this.UnspentAttributePoints -= attributeValue;
        if (attributeValue == 0)
          this.AddAttribute(characterAttribute, 1, true);
      }
      foreach (SkillObject skill in (List<SkillObject>) Skills.All)
      {
        this.UnspentFocusPoints -= this.GetFocus(skill);
        this.InitializeSkillXp(skill);
      }
    }

    private void DistributeUnspentAttributePoints()
    {
      while (this.UnspentAttributePoints > 0)
      {
        CharacterAttribute attributeToUpgrade = Campaign.Current.Models.CharacterDevelopmentModel.GetNextAttributeToUpgrade(this.Hero);
        if (attributeToUpgrade == null)
          break;
        this.AddAttribute(attributeToUpgrade, 1, true);
      }
    }

    private void ClearHeroLevel() => this.Hero.Level = 0;

    public void AddPerk(PerkObject perk) => this.Hero.SetPerkValueInternal(perk, true);

    private void OnGainLevel(bool shouldNotify = true)
    {
      this.UnspentFocusPoints += Campaign.Current.Models.CharacterDevelopmentModel.FocusPointsPerLevel;
      if (this.Hero.Level % Campaign.Current.Models.CharacterDevelopmentModel.LevelsPerAttributePoint == 0)
        ++this.UnspentAttributePoints;
      CampaignEventDispatcher.Instance.OnHeroLevelledUp(this.Hero, shouldNotify);
    }

    public int GetXpRequiredForLevel(int level)
    {
      return Campaign.Current.Models.CharacterDevelopmentModel.SkillsRequiredForLevel(level);
    }

    public void RemoveAttribute(CharacterAttribute attrib, int changeAmount)
    {
      if (changeAmount == 0)
        return;
      int num = this.Hero.GetAttributeValue(attrib) - changeAmount;
      this.Hero.SetAttributeValueInternal(attrib, num);
    }

    public void AddAttribute(CharacterAttribute attrib, int changeAmount, bool checkUnspentPoints = true)
    {
      if (changeAmount == 0)
        return;
      int attributeValue = this.Hero.GetAttributeValue(attrib);
      if (attributeValue + changeAmount > Campaign.Current.Models.CharacterDevelopmentModel.MaxAttribute || this.UnspentAttributePoints < 1 & checkUnspentPoints)
        return;
      int num = attributeValue + changeAmount;
      this.Hero.SetAttributeValueInternal(attrib, num);
      if (!checkUnspentPoints)
        return;
      --this.UnspentAttributePoints;
    }

    private void ClearFocuses() => this._newFocuses.ClearAllProperty();

    public void AddFocus(SkillObject skill, int changeAmount, bool checkUnspentFocusPoints = true)
    {
      int focus = this.GetFocus(skill);
      int pointsToAddFocus = this.GetRequiredFocusPointsToAddFocus(skill);
      int num = changeAmount;
      int newAmount = focus + num;
      this.SetFocus(skill, newAmount);
      this.UnspentFocusPoints = checkUnspentFocusPoints ? this.UnspentFocusPoints - pointsToAddFocus : this.UnspentFocusPoints;
    }

    public void RemoveFocus(SkillObject skill, int changeAmount)
    {
      int newAmount = this.GetFocus(skill) - changeAmount;
      this.SetFocus(skill, newAmount);
    }

    public bool CanAddFocusToSkill(SkillObject skill)
    {
      return this.GetFocus(skill) < Campaign.Current.Models.CharacterDevelopmentModel.MaxFocusPerSkill && this.UnspentFocusPoints >= this.GetRequiredFocusPointsToAddFocus(skill);
    }

    public int GetRequiredFocusPointsToAddFocus(SkillObject skill) => 1;

    private void SetFocus(SkillObject focus, int newAmount)
    {
      this._newFocuses.SetPropertyValue(focus, newAmount);
    }

    public int GetFocus(SkillObject skill) => this._newFocuses.GetPropertyValue(skill);

    private void DistributeUnspentFocusPoints()
    {
      while (this.UnspentFocusPoints > 0)
      {
        SkillObject nextSkillToAddFocus = Campaign.Current.Models.CharacterDevelopmentModel.GetNextSkillToAddFocus(this.Hero);
        if (nextSkillToAddFocus == null)
          break;
        this.AddFocus(nextSkillToAddFocus, 1, true);
      }
    }

    public bool GetPerkValue(PerkObject perk) => this.Hero.GetPerkValue(perk);

    private void SelectPerks()
    {
      foreach (PerkObject perk in (List<PerkObject>) PerkObject.All)
      {
        if ((double) this.Hero.GetSkillValue(perk.Skill) >= (double) perk.RequiredSkillValue && !this.Hero.GetPerkValue(perk) && (perk.AlternativePerk == null || !this.Hero.GetPerkValue(perk.AlternativePerk)))
        {
          PerkObject nextPerkToChoose = Campaign.Current.Models.CharacterDevelopmentModel.GetNextPerkToChoose(this.Hero, perk);
          if (nextPerkToChoose != null)
            this.AddPerk(nextPerkToChoose);
        }
      }
    }

    public void InitializeSkillXp(SkillObject skill)
    {
      int requiredForSkillLevel = Campaign.Current.Models.CharacterDevelopmentModel.GetXpRequiredForSkillLevel(this.Hero.GetSkillValue(skill));
      this.SetPropertyValue((PropertyObject) skill, (float) requiredForSkillLevel);
    }

    protected override void AfterLoad()
    {
      if (MBSaveLoad.IsUpdatingGameVersion && MBSaveLoad.LastLoadedGameVersion < ApplicationVersion.FromString("v1.2.6"))
      {
        foreach (CharacterAttribute charAttribute in (List<CharacterAttribute>) Attributes.All)
        {
          if (this.Hero.GetAttributeValue(charAttribute) == 0)
          {
            this.InitializeHeroDeveloper(false, (CharacterObject) null);
            break;
          }
        }
      }
      if (!MBSaveLoad.IsUpdatingGameVersion || !(MBSaveLoad.LastLoadedGameVersion < ApplicationVersion.FromString("v1.2.0")))
        return;
      if ((double) this.Hero.Age >= (double) Campaign.Current.Models.AgeModel.HeroComesOfAge && Campaign.Current.Models.CharacterDevelopmentModel.SkillsRequiredForLevel(this.Hero.Level) > this.TotalXp)
      {
        this.TotalXp = Campaign.Current.Models.CharacterDevelopmentModel.SkillsRequiredForLevel(this.Hero.Level);
        this.CheckLevel();
      }
      foreach (SkillObject skill in (List<SkillObject>) Skills.All)
      {
        if (this.GetSkillXpProgress(skill) < 0)
          this.InitializeSkillXp(skill);
      }
    }
  }
}
